package com.googlecode.afx;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.FatalBeanException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.googlecode.afx.splash.AFXSplashScreen;
import com.googlecode.afx.splash.impl.AFXSplashScreenImpl;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.stage.Stage;

/**
 * Abstract base-class for JFX applications. This class takes care for loading
 * the Spring context. It is also able to display a splash screen while
 * context loading takes place.
 * <p>
 * Derived classes must implement the method
 * <pre>
 * public abstract void startInternal(Stage stage) throws Exception
 * </pre>
 * to start the application after the Spring context is successfully loaded.
 * 
 * @author MartinKoster
 *
 */
public abstract class AFXApplication extends Application {

	private static final Log LOG = LogFactory.getLog(AFXApplication.class);
	
	private ApplicationContext applicationContext;

	/**
	 * Method to override for starting the JavaFX application.
	 * 
	 * @param stage
	 * @throws Exception
	 */
	public abstract void startInternal(Stage stage) throws Exception;

	/**
	 * Returns the location of the Spring application context.
	 * 
	 * @return
	 */
	public abstract String getApplicationContextLocation();

	/**
	 * Returns a splash screen for application startup. Can be overridden by derived classes that return
	 * a custom splash screen.
	 * 
	 * @return
	 */
	public AFXSplashScreen getSplashScreen() {
		return new AFXSplashScreenImpl();
	}
		
	@Override
	public void start(final Stage stage) throws Exception {
		
		// initialize Spring in a background task, call "startInternal" in the "succeeded" method.
		Task<Void> task = new Task<Void>() {
			@Override
			protected Void call() throws Exception {
				updateMessage("Loading application...");
				initializeSpring(stage);
				updateMessage("Application loaded successfully.");
				return null;
			}

			@Override
			protected void succeeded() {
				try {
					startInternal(stage);
				}
				catch(Exception e) {
					LOG.error("Failed to start application!", e);
					Platform.exit();
				}
			}
		};
		
		LOG.debug("Starting async task.");

		// if there is a splash screen for this application, display it while the application is starting.
		AFXSplashScreen splashScreen = this.getSplashScreen();
		if(splashScreen != null) {
			splashScreen.showSplashScreen(task);
		}
		
		Thread thread = new Thread(task);
		thread.setDaemon(true);
		thread.start();
	}

	/**
	 * Initialize Spring context and the <code>JFXViewFactory</code>.
	 * 
	 * @param stage
	 *            the primary stage
	 */
	private void initializeSpring(Stage stage) {

		// initialize Spring context
		this.applicationContext = new ClassPathXmlApplicationContext(this.getApplicationContextLocation());

		// initialize the PrimaryStageHolder
		AFXPrimaryStageHolder primaryStageHolder = (AFXPrimaryStageHolder) this.applicationContext
				.getBean(AFXPrimaryStageHolder.class);

		if (primaryStageHolder == null) {
			throw new FatalBeanException("Type 'PrimaryStageHolder' must be defined in the Spring context!");
		}
		
		primaryStageHolder.setPrimaryStage(stage);
	}

	public ApplicationContext getApplicationContext() {
		return applicationContext;
	}
	

}
